华为三场比赛
[12.23] 鲲鹏计算专场
mips
mips架构。
ida反编译以后可以看到
v4是我们输入的字符串,很明显是迷宫逻辑,上下左右用wasd走,迷宫存在dword_100111F0里。
sub_10000744()这个初始函数是用来找起点用的(就是迷宫中3所在的地方,在后面可以看到3其实表示的是当前位置)。
这里也可以看到应该有多个迷宫(dword_10011D10是用来表示第几个迷宫的,且<=2,一个迷宫有225个数)+一个迷宫宽为15=三个迷宫,每个迷宫为15*15。
然后就是下面的四个函数,随便挑一个出来(比如sub_10000D28())可以看到
很明显是个往右走的函数,3表示当前位置,并把上一个当前位置标为1(可走路径)。并且可以看到终点是4,就是说我们要把每个迷宫从3走到4。
dump迷宫数组,写脚本打印迷宫:
aMap=[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 3, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0] |
可以看到打印出了三个迷宫,为了看得清楚所以选用几个特定字符打印。
.....********** |
走迷宫,然后把路径拼起来,根据提示转md5,get flag。
(有个疑惑哈,第二个迷宫理论上说就算是最短路也有多解?是题目出锅了还是我哪里看漏了= =
(再补一句,题目似乎甚至没要求最短路???神奇.jpg
import hashlib |
flag{999ea6aa6c365ab43eec2a0f0e5968d5}
pypy
把题目文件拖进ida,搜索字符串能看到
猜测是pyinstaller打包的文件。
也就是这个题让我突然发现pyinstaller还能打包成elf的,于是比赛结束以后赶紧把之前总结的解包指南更新了:RE套路 - 关于pyinstaller打包文件的复原 | c10udlnk_Log。
走流程解包,得到python源码。
看到这种混淆变量名,果断替换成ida style变量名(。
放一下源码:
# uncompyle6 version 3.7.4 |
可以看到最后getflag这里(func())的程序逻辑就一个rc4加密,由rc4的特性可知加密和解密流程相同,故复用程序中的rc4()来得到flag。
uncompyle反编译出来的源码是python3,但是题目本身的源码是python2,注意编码问题。
关于编码问题,可以看:
这里因为反编译做了转换成python3的处理,所以脚本用python3写。
DEFAULT_KEY = u'Y\xf3\x02\xc3%\x9a\x820\x0b\xbb%\x7f~;\xd2\xdc' |
flag{snake_bao_is_really_lucky}
print【TODO】
【TODO】
这个题感觉大概知道怎么做,但就是不会啊(等wp…
贴一下当时的想法,看了看逻辑只有sprintf这种函数,除此以外没有别的可以改写内存数据的操作了。
动态调试跟了一下,猜测是sprintf格式化字符串漏洞写入?
pwn太菜了还没搞懂要怎么往output那里写(虽然这是逆向题orz
setup函数那里有一些format的初始化,主要是loop()那里,控制input(输入的字符串,全部为可见字符且长度>11),来改变使得output!=原来的output且output-1==48(‘0’)。
[12.27] HarmonyOS和HMS专场
re123
用file命令可以看到是MS Windows HtmlHelp Data文件(即.chm),查看文件头也可以知道。
所以添加后缀名.chm。
关于chm文件有一个常用的反编译器ChmDecompiler,可以释放CHM里面的全部源文件(包括网页、文本、图片、CHM、ZIP、EXE等全部源文件),并且完美地恢复源文件的全部目录结构 (摘抄的简介。
所以用ChmDecompiler打开re.chm,解压缩,可以看到目录下出现一个包含四个文件的文件夹(其实源文件只有三个,.hhp是ChmDecompiler自动生成的)。
一个一个翻可以看到doc.htm里有一段奇怪的Item1。
大概可以看到是powershell的语法?(感觉像win后门,这么多no的参数
查了一下其实就是把后面那大段进行base64解码而已,用wsl解一下base64有
然后得到了一段.NET代码(白字)。
通过查微软文档可以知道,这里是把base64解码以后的字符进行Deflate解压的过程,所以用脚本把中间那段base64解码,并整理输出。
import base64 |
很明显的逻辑了,把doc.chm(应该是原来的re.chm)中”xxxxxxxx”后面的部分提取出来,还是用base64解码得到文件。
把这后面的内容手动复制出来到cont.txt里,进行base64解码,最后存在theFile中。
base64 -d cont.txt > theFile |
查看theFile可以猜测是exe(毕竟最开始给的就是有powershell指令的base64),把文件头补上,并改后缀名(即theFile.exe)。
用ida打开,通过FindCrypt插件可以看到AES,跟过去能看到AES加密时的S盒(其实这里前两个都是S盒,第三个是逆S盒),猜测用到了AES加密。
往上回溯找到主函数
显然,这里是AES加密过程,sub_180001100()是密钥拓展过程,sub_1800015B0()是AES加密。
看了一下感觉是原装无魔改的AES,密文密钥都给了,那就直接写脚本解密。
注意这里是以整数形式给出的,别忘了小端序。
from Crypto.Cipher import AES |
flag{youcangues}
puzzle
mips架构。
加载进ida以后,通过字符串回溯找到主函数。
可以看到很明显的sub_401134()这个check,先往这里面看。
看到是一个疑似maze的逻辑(
不过sub_400FA8()点进去以后可以看到是swap的功能
所以应该不是maze,是一个以交换为主的逻辑。
至于dword_4A0010,可以看到是一个九个数的数组。
v4和v5的出处在switch逻辑上面一点
可以看到最后(v4,v5)其实表示了数组里0的位置,且数组实际可以看成是3*3。
即:
4 0 3 |
最后sub_400FFC()的检查逻辑:
实际上就是要让这个3*3等于
1 2 3 |
把0看成空位的话,很容易就想到3*3的华容道了。
(或者玩算法的小伙伴可能对八数码问题这个名字更熟悉?
有本事下次出数织啊!20*20我都给你火速解出来(来自数织爱好者的吐槽)
这里实际上是求最短能得到的路径(15步),懒得想了,直接去网上抓了个现成代码下来改了改。
#include <iostream> |
得到每一步的情况,进而根据switch写出路径。
第 0 步 |
路径为“884226886224488”。
接下来看主函数里check上面的部分,看到sub_409070()实际上是一个scanf,而dword_4A1B60是我们的输入,也就是最后的flag,中间对输入进行处理以后才得到“884226886224488”这个字符串。
在里面翻可以翻到一个sub_400B58(),猜测是base64换表编码。
于是尝试写脚本编码。
import base64 |
试试能不能过check。
wsl运行:(要装qemu才能执行,毕竟特殊架构。
cp $(which qemu-mips) . |
执行mips程序,输入脚本中解出的字符串,发现成功了,get flag。
flag{8xOi6R2k8xOk6R2i7xOm}
aRm
arm架构。
照例通过字符串回溯找到主函数。
v1是key,v9是输入的flag,对输入的限制就是长度为42且头尾是“flag{”和“}”。
动态调一下可以发现,sub_27770()这个函数实际上是把unk_723A0数组里的42个数据复制到v8里。
./qemu-arm -L ./ -g 12345 ./aRm |
(Debugger选Remote GDB debugger,把端口号填上就好,其余配置具体见RE套路 - 关于使用IDA 7.0前端进行的动态调试 | c10udlnk_Log中调试elf部分。
现在我们未知的数就剩v5和v6了,v5要看sub_1714C()的输出,v6这里相当于是42条42元一次方程组(输入未知的情况下)。
而sub_105B4()是输出42个结果,于是可以知道只要输出了output.txt里的42个数就是正确的flag了。
由于前面有一个sub_169AC(key),这边又是一个无参的sub_1714C()+1,于是猜测是srand(seed)和rand()。
为了证明猜测,多次运行程序输入同一个key和相同/不同的flag,发现每一次的v5是一样的,结合rand()的伪随机性,确定这就是随机函数。
由于key只有一字节(0~255),干脆直接爆破。把output.txt的数据读入,用sympy库解方程,只要第一个解x0等于ord('f')^v8[0]=102^0xA0=198
,就说明这个key有极大可能性是正确的key。
当然,在此之前,我们得先知道每一次的v5(即方程的系数)是多少。
于是hook函数,在v5生成之后复用程序原来就有的print函数及格式符,把每次生成的v5都打印出来。
还记得有个函数是可以输出八位十六进制数的吧,就是那个sub_105B4(),我们可以用这里面的printf,然后把调用这个函数的地方nop掉(目标要明确,现在是为了爆破key,没必要管程序的正常性hahah)。
本来是想自己堆个调用printf出来的,不知道为什么keypatch对
LDR R0, =a08x
解释不了,于是只好绕个小路了。
转到汇编窗口,记一下这里的loc,等会要跳过来的。
看回去原来二重循环里出v5那个地方
这几条语句的意思就是f5里面的那行v5 = (unsigned __int8)(sub_1714C() + 1);
,我们从再下一行开始改。
注意可以改的范围在蓝框这里,这是我们不需要的v6[j] += (unsigned __int8)v9[k] * v5;
,在这个范围里可以尽情修改,剩下的nop掉。
用keypatch直接输入汇编,patch后面的语句为
(其实就是改了一行B loc_105D4
,剩下的直接Fill with NOPs就好)
接下来去往loc_105D4,改造一下。
我们知道,现在R3寄存器里实际上存的是v5的值,我们调用printf直接输出R3的值就能达成目标。
在ARM汇编里,函数传参用R0、R1……所以我们这里给R1一个R3的值就好。
这里本来就是MOV R1, R3
不用改,所以直接把前面nop掉。
因为v5那里是取(unsigned __int8),所以把这里改一下,把”%08x”改成”%02x”,就是出来的v5。
别忘了后面还要跳回去,找到地址:
patch:
记得把调用sub_105B4()的地方也nop掉。
最后把patch的字节保存一下。
运行测试一下,有:
ok,hook成功,开始爆破。
import pexpect |
爆破得到:
可知key是82,而v9在xor以后的数组也爆出来了,简单xor得flag:
arr=[0xA0, 0xE4, 0xBA, 0xFB, 0x10, 0xDD, 0xAC, 0x65, 0x8D, 0x0B, 0x57, 0x1A, 0xE4, 0x28, 0x96, 0xB3, 0x0C, 0x79, 0x4D, 0x80, 0x90, 0x99, 0x58, 0xFE, 0x50, 0xD3, 0xF9, 0x3C, 0x0F, 0xC1, 0xE3, 0xA6, 0x39, 0xC3, 0x28, 0x75, 0xF8, 0xC9, 0xC8, 0xCD, 0x78, 0x26] |
flag{94bb46eb-a0a2-4a4a-a3d5-2ba877deb448}
pe
arm架构,没环境调不动,只能硬看了XD。这题有好多奇怪的函数,而且通过伪代码跟的话就能看到函数套函数套函数……所以基本靠猜出来的(
继续通过字符串回溯找主函数。
根据参数猜测,sub_1400023C8()是strcmp()的作用,我们需要让v9=”KIMLXDWRZXTHXTHQTXTXHZWC”。
再往上走,sub_1400015B0这个函数调用了v9,于是跟进去看功能。
感觉是某种加密,以相邻的两字符为一组,对这两个字符做相同的操作,再做后续处理。
跟进sub_1400012B8()里看,可以看到大概是一个搜索的过程
如果不等于-1就说明在表中找到了这个元素,然后返回一个索引(?
再往下看好像就看不太懂了,然后就是玄学的猜猜猜= =
回去看string可以看到一个这个,猜测是密钥表之类的?
往上回溯也看不到什么线索,不过可以发现这25个数字刚好没有相同的。
现在总结一下这个古典加密算法的特点,大概是两个为一组处理+已定义的密钥表(即不是通过输入生成的)5*5+处理时用到索引。
很久很久以前想写某对cp的AU同人时想把ctf元素混进去,就看了很多简单又奇奇怪怪的编码/古典密码(现代密码太学术了XD),没想到现在有用武之地了(手动狗头。
然后翻到了一个符合这个特点的密码,Playfair Cipher:
不同的是密码表是直接给出的,不过加密流程再对回ida里的反编译感觉挺像的,于是果断试试。
按照Playfair Cipher的加解密流程写出脚本:
def getIndex(c): |
走一遍脚本解密可以得到:
YES MAYBE YOU CAN RUN AN ARM PE
No, I can’t 😦
看起来能读的通,成功get flag。
flag{YESMAYBEYOUCANRUNANARMPE}